/******************************************************************************
 *
 * Project:  CSV Translator
 * Purpose:  Implements OGRCSVLayer class.
 * Author:   Frank Warmerdam <warmerdam@pobox.com>
 *
 ******************************************************************************
 * Copyright (c) 2004, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2008-2014, Even Rouault <even dot rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "cpl_port.h"
#include "ogr_csv.h"

#include <cerrno>
#include <climits>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#if HAVE_FCNTL_H
#  include <fcntl.h>
#endif
#include <algorithm>
#include <limits>
#include <string>
#include <vector>

#include "cpl_conv.h"
#include "cpl_csv.h"
#include "cpl_error.h"
#include "cpl_string.h"
#include "cpl_vsi.h"
#include "ogr_api.h"
#include "ogr_core.h"
#include "ogr_feature.h"
#include "ogr_geometry.h"
#include "ogr_p.h"
#include "ogr_spatialref.h"
#include "ogrsf_frmts.h"

#define DIGIT_ZERO '0'

CPL_CVSID("$Id$")

/************************************************************************/
/*                            CSVSplitLine()                            */
/*                                                                      */
/*      Tokenize a CSV line into fields in the form of a string         */
/*      list.  This is used instead of the CPLTokenizeString()          */
/*      because it provides correct CSV escaping and quoting            */
/*      semantics.                                                      */
/************************************************************************/

static char **CSVSplitLine( const char *pszString, char chDelimiter,
                            bool bKeepLeadingAndClosingQuotes,
                            bool bMergeDelimiter )

{
    CPLStringList aosRetList;

    char *pszToken = static_cast<char *>(CPLCalloc(10, 1));
    int nTokenMax = 10;

    while( pszString != nullptr && *pszString != '\0' )
    {
        bool bInString = false;

        int nTokenLen = 0;

        // Try to find the next delimiter, marking end of token.
        for( ; *pszString != '\0'; pszString++ )
        {
            // End if this is a delimiter skip it and break.
            if( !bInString && *pszString == chDelimiter )
            {
                pszString++;
                if( bMergeDelimiter )
                {
                    while( *pszString == chDelimiter )
                        pszString++;
                }
                break;
            }

            if( *pszString == '"' )
            {
                if( !bInString || pszString[1] != '"' )
                {
                    bInString = !bInString;
                    if( !bKeepLeadingAndClosingQuotes )
                        continue;
                }
                else  // Doubled quotes in string resolve to one quote.
                {
                    pszString++;
                }
            }

            if( nTokenLen >= nTokenMax - 2 )
            {
                nTokenMax = nTokenMax * 2 + 10;
                pszToken = static_cast<char *>(CPLRealloc(pszToken, nTokenMax));
            }

            pszToken[nTokenLen] = *pszString;
            nTokenLen++;
        }

        pszToken[nTokenLen] = '\0';
        aosRetList.AddString(pszToken);

        // If the last token is an empty token, then we have to catch
        // it now, otherwise we won't reenter the loop and it will be lost.
        if( *pszString == '\0' && *(pszString - 1) == chDelimiter )
        {
            aosRetList.AddString("");
        }
    }

    CPLFree(pszToken);

    if( aosRetList.Count() == 0 )
        return static_cast<char **>(CPLCalloc(sizeof(char *), 1));
    else
        return aosRetList.StealList();
}

/************************************************************************/
/*                      OGRCSVReadParseLineL()                          */
/*                                                                      */
/*      Read one line, and return split into fields.  The return        */
/*      result is a stringlist, in the sense of the CSL functions.      */
/************************************************************************/

char **OGRCSVReadParseLineL( VSILFILE *fp, char chDelimiter,
                             bool bDontHonourStrings,
                             bool bKeepLeadingAndClosingQuotes,
                             bool bMergeDelimiter )

{
    const char *pszLine = CPLReadLineL(fp);
    if( pszLine == nullptr )
        return nullptr;

    // Skip BOM.
    const GByte *pabyData = reinterpret_cast<const GByte *>(pszLine);
    if( pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF )
        pszLine += 3;

    // Special fix to read NdfcFacilities.xls with un-balanced double quotes.
    if( chDelimiter == '\t' && bDontHonourStrings )
    {
        return CSLTokenizeStringComplex(pszLine, "\t", FALSE, TRUE);
    }

    // If there are no quotes, then this is the simple case.
    // Parse, and return tokens.
    if( strchr(pszLine,'\"') == nullptr )
        return CSVSplitLine(pszLine, chDelimiter, bKeepLeadingAndClosingQuotes,
                            bMergeDelimiter);

    // We must now count the quotes in our working string, and as
    // long as it is odd, keep adding new lines.
    char *pszWorkLine = CPLStrdup(pszLine);

    int i = 0;
    int nCount = 0;
    size_t nWorkLineLength = strlen(pszWorkLine);

    while( true )
    {
        for( ; pszWorkLine[i] != '\0'; i++ )
        {
            if( pszWorkLine[i] == '\"' )
                nCount++;
        }

        if( nCount % 2 == 0 )
            break;

        pszLine = CPLReadLineL(fp);
        if( pszLine == nullptr )
            break;

        const size_t nLineLen = strlen(pszLine);

        char *pszWorkLineTmp = static_cast<char *>(
            VSI_REALLOC_VERBOSE(pszWorkLine, nWorkLineLength + nLineLen + 2));
        if( pszWorkLineTmp == nullptr )
            break;
        pszWorkLine = pszWorkLineTmp;

        // The '\n' gets lost in CPLReadLine().
        strcat(pszWorkLine + nWorkLineLength, "\n");
        strcat(pszWorkLine + nWorkLineLength, pszLine);

        nWorkLineLength += nLineLen + 1;
    }

    char **papszReturn =
        CSVSplitLine(pszWorkLine, chDelimiter, bKeepLeadingAndClosingQuotes,
                     bMergeDelimiter);

    CPLFree(pszWorkLine);

    return papszReturn;
}

/************************************************************************/
/*                            OGRCSVLayer()                             */
/*                                                                      */
/*      Note that the OGRCSVLayer assumes ownership of the passed       */
/*      file pointer.                                                   */
/************************************************************************/

OGRCSVLayer::OGRCSVLayer( const char *pszLayerNameIn,
                          VSILFILE  *fp, const char *pszFilenameIn,
                          int bNewIn, int bInWriteModeIn,
                          char chDelimiterIn ) :
    poFeatureDefn(nullptr),
    fpCSV(fp),
    nNextFID(1),
    bHasFieldNames(false),
    bNew(CPL_TO_BOOL(bNewIn)),
    bInWriteMode(CPL_TO_BOOL(bInWriteModeIn)),
    bUseCRLF(false),
    bNeedRewindBeforeRead(false),
    eGeometryFormat(OGR_CSV_GEOM_NONE),
    pszFilename(CPLStrdup(pszFilenameIn)),
    bCreateCSVT(false),
    bWriteBOM(false),
    chDelimiter(chDelimiterIn),
    nCSVFieldCount(0),
    panGeomFieldIndex(nullptr),
    bFirstFeatureAppendedDuringSession(true),
    bHiddenWKTColumn(false),
    iNfdcLongitudeS(-1),
    iNfdcLatitudeS(-1),
    bDontHonourStrings(false),
    iLongitudeField(-1),
    iLatitudeField(-1),
    iZField(-1),
    bIsEurostatTSV(false),
    nEurostatDims(0),
    nTotalFeatures(bNewIn ? 0 : -1),
    bWarningBadTypeOrWidth(false),
    bKeepSourceColumns(false),
    bKeepGeomColumns(true),
    bMergeDelimiter(false),
    bEmptyStringNull(false)
{
    poFeatureDefn = new OGRFeatureDefn(pszLayerNameIn);
    SetDescription(poFeatureDefn->GetName());
    poFeatureDefn->Reference();
    poFeatureDefn->SetGeomType(wkbNone);
}

/************************************************************************/
/*                             Matches()                                */
/************************************************************************/

bool OGRCSVLayer::Matches( const char *pszFieldName, char **papszPossibleNames )
{
    if( papszPossibleNames == nullptr )
        return false;
    for( char **papszIter = papszPossibleNames; *papszIter; papszIter++ )
    {
        const char *pszPattern = *papszIter;
        const char *pszStar = strstr(pszPattern, "*");
        if( pszStar == nullptr )
        {
            if( EQUAL(pszFieldName, pszPattern) )
                return true;
        }
        else
        {
            if( pszStar == pszPattern )
            {
                if( strlen(pszPattern) >= 3 &&
                    pszPattern[strlen(pszPattern) - 1] == '*' )
                {
                    // *pattern*
                    CPLString oPattern(pszPattern + 1);
                    oPattern.resize(oPattern.size() - 1);
                    if( CPLString(pszFieldName).ifind(oPattern) !=
                        std::string::npos )
                        return true;
                }
                else
                {
                    // *pattern
                    if( strlen(pszFieldName) >= strlen(pszPattern) - 1 &&
                        EQUAL(pszFieldName + strlen(pszFieldName) -
                                  (strlen(pszPattern) - 1),
                              pszPattern + 1) )
                    {
                        return true;
                    }
                }
            }
            else if( pszPattern[strlen(pszPattern) - 1] == '*' )
            {
                // pattern*
                if( EQUALN(pszFieldName, pszPattern, strlen(pszPattern) - 1) )
                    return true;
            }
        }
    }
    return false;
}

/************************************************************************/
/*                      BuildFeatureDefn()                              */
/************************************************************************/

void OGRCSVLayer::BuildFeatureDefn( const char *pszNfdcGeomField,
                                    const char *pszGeonamesGeomFieldPrefix,
                                    char **papszOpenOptions )
{
    bMergeDelimiter = CPLFetchBool(papszOpenOptions, "MERGE_SEPARATOR", false);
    bEmptyStringNull =
        CPLFetchBool(papszOpenOptions, "EMPTY_STRING_AS_NULL", false);

    // If this is not a new file, read ahead to establish if it is
    // already in CRLF (DOS) mode, or just a normal unix CR mode.
    if( !bNew && bInWriteMode )
    {
        int nBytesRead = 0;
        char chNewByte = '\0';

        while( nBytesRead < 10000 && VSIFReadL(&chNewByte, 1, 1, fpCSV) == 1 )
        {
            if( chNewByte == 13 )
            {
                bUseCRLF = true;
                break;
            }
            nBytesRead++;
        }
        VSIRewindL(fpCSV);
    }

    // Check if the first record seems to be field definitions or
    // not.  We assume it is field definitions if the HEADERS option
    // not supplied and none of the values are strictly numeric.
    char **papszTokens = nullptr;
    int nFieldCount = 0;

    if( !bNew )
    {
        const char *pszLine = CPLReadLineL(fpCSV);
        if( pszLine != nullptr )
        {
            // Detect and remove UTF-8 BOM marker if found (#4623).
            if( reinterpret_cast<const unsigned char *>(pszLine)[0] == 0xEF &&
                reinterpret_cast<const unsigned char *>(pszLine)[1] == 0xBB &&
                reinterpret_cast<const unsigned char *>(pszLine)[2] == 0xBF )
            {
                pszLine += 3;
            }

            // Tokenize the strings and preserve quotes, so we can separate
            // string from numeric this is only used in the test for
            // bHasFieldNames (bug #4361).
            const char szDelimiter[2] = { chDelimiter, '\0' };
            papszTokens =
                CSLTokenizeString2(pszLine, szDelimiter,
                                   (CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS |
                                    CSLT_PRESERVEQUOTES));
            nFieldCount = CSLCount(papszTokens);

            if( nFieldCount > 0 && papszTokens[0][0] == '"' )
                m_eStringQuoting = StringQuoting::ALWAYS;

            const char *pszCSVHeaders =
                CSLFetchNameValueDef(papszOpenOptions, "HEADERS", "AUTO");

            if( EQUAL(pszCSVHeaders, "YES") )
            {
                bHasFieldNames = true;
            }
            else if( EQUAL(pszCSVHeaders, "NO") )
            {
                bHasFieldNames = false;
            }
            else
            {
                // Detect via checking for the presence of numeric values.
                bHasFieldNames = true;
                for( int iField = 0;
                     iField < nFieldCount && bHasFieldNames;
                     iField++ )
                {
                    const CPLValueType eType =
                        CPLGetValueType(papszTokens[iField]);
                    if( eType == CPL_VALUE_INTEGER ||
                        eType == CPL_VALUE_REAL )
                    {
                        // We have a numeric field, therefore do not consider
                        // the first line as field names.
                        bHasFieldNames = false;
                    }
                }

                const CPLString osExt =
                    OGRCSVDataSource::GetRealExtension(pszFilename);

                // Eurostat .tsv files.
                if( EQUAL(osExt, "tsv") && nFieldCount > 1 &&
                    strchr(papszTokens[0], ',') != nullptr &&
                    strchr(papszTokens[0], '\\') != nullptr )
                {
                    bHasFieldNames = true;
                    bIsEurostatTSV = true;
                }
            }

            // Tokenize without quotes to get the actual values.
            CSLDestroy(papszTokens);
            int l_nFlags = CSLT_HONOURSTRINGS;
            if( !bMergeDelimiter )
                l_nFlags |= CSLT_ALLOWEMPTYTOKENS;
            papszTokens = CSLTokenizeString2(pszLine, szDelimiter, l_nFlags);
            nFieldCount = CSLCount(papszTokens);
        }
    }
    else
    {
        bHasFieldNames = false;
    }

    if( !bNew )
        ResetReading();

    nCSVFieldCount = nFieldCount;

    panGeomFieldIndex = static_cast<int *>(CPLCalloc(nFieldCount, sizeof(int)));
    for( int iField = 0; iField < nFieldCount; iField++ )
    {
        panGeomFieldIndex[iField] = -1;
    }

    // Check for geonames.org tables.
    if( !bHasFieldNames && nFieldCount == 19 )
    {
        if( CPLGetValueType(papszTokens[0]) == CPL_VALUE_INTEGER &&
            CPLGetValueType(papszTokens[4]) == CPL_VALUE_REAL &&
            CPLGetValueType(papszTokens[5]) == CPL_VALUE_REAL &&
            CPLAtof(papszTokens[4]) >= -90 && CPLAtof(papszTokens[4]) <= 90 &&
            CPLAtof(papszTokens[5]) >= -180 && CPLAtof(papszTokens[4]) <= 180 )
        {
            CSLDestroy(papszTokens);
            papszTokens = nullptr;

            static const struct {
                const char *pszName;
                OGRFieldType eType;
            }
            asGeonamesFieldDesc[] =
            {
                { "GEONAMEID", OFTString },
                { "NAME", OFTString },
                { "ASCIINAME", OFTString },
                { "ALTNAMES", OFTString },
                { "LATITUDE", OFTReal },
                { "LONGITUDE", OFTReal },
                { "FEATCLASS", OFTString },
                { "FEATCODE", OFTString },
                { "COUNTRY", OFTString },
                { "CC2", OFTString },
                { "ADMIN1", OFTString },
                { "ADMIN2", OFTString },
                { "ADMIN3", OFTString },
                { "ADMIN4", OFTString },
                { "POPULATION", OFTReal },
                { "ELEVATION", OFTInteger },
                { "GTOPO30", OFTInteger },
                { "TIMEZONE", OFTString },
                { "MODDATE", OFTString }
            };
            for( int iField = 0; iField < nFieldCount; iField++)
            {
                OGRFieldDefn oFieldDefn(asGeonamesFieldDesc[iField].pszName,
                                        asGeonamesFieldDesc[iField].eType);
                poFeatureDefn->AddFieldDefn(&oFieldDefn);
            }

            iLatitudeField = 4;
            iLongitudeField = 5;

            nFieldCount = 0;

            bDontHonourStrings = true;
        }
    }

    // Search a csvt file for types.
    char **papszFieldTypes = nullptr;
    if( !bNew )
    {
        char *dname = CPLStrdup(CPLGetDirname(pszFilename));
        char *fname = CPLStrdup(CPLGetBasename(pszFilename));
        VSILFILE *fpCSVT =
            VSIFOpenL(CPLFormFilename(dname, fname, ".csvt"), "r");
        CPLFree(dname);
        CPLFree(fname);
        if( fpCSVT != nullptr )
        {
            VSIRewindL(fpCSVT);
            papszFieldTypes = OGRCSVReadParseLineL(fpCSVT, ',', false, false);
            VSIFCloseL(fpCSVT);
        }
    }

    // Optionally auto-detect types.
    if( !bNew && papszFieldTypes == nullptr &&
        CPLTestBool(
            CSLFetchNameValueDef(papszOpenOptions, "AUTODETECT_TYPE", "NO")) )
    {
        papszFieldTypes = AutodetectFieldTypes(papszOpenOptions, nFieldCount);
        if( papszFieldTypes != nullptr )
        {
            bKeepSourceColumns = CPLTestBool(CSLFetchNameValueDef(
                papszOpenOptions, "KEEP_SOURCE_COLUMNS", "NO"));
        }
    }

    char **papszGeomPossibleNames = CSLTokenizeString2(
        CSLFetchNameValue(papszOpenOptions, "GEOM_POSSIBLE_NAMES"), ",", 0);
    char **papszXPossibleNames = CSLTokenizeString2(
        CSLFetchNameValue(papszOpenOptions, "X_POSSIBLE_NAMES"), ",", 0);
    char **papszYPossibleNames = CSLTokenizeString2(
        CSLFetchNameValue(papszOpenOptions, "Y_POSSIBLE_NAMES"), ",", 0);
    char **papszZPossibleNames = CSLTokenizeString2(
        CSLFetchNameValue(papszOpenOptions, "Z_POSSIBLE_NAMES"), ",", 0);
    bKeepGeomColumns = CPLTestBool(
        CSLFetchNameValueDef(papszOpenOptions, "KEEP_GEOM_COLUMNS", "YES"));

    // Build field definitions.
    poFeatureDefn->ReserveSpaceForFields(nFieldCount);

    constexpr int knMAX_GEOM_COLUMNS = 100;
    bool bWarnedMaxGeomFields = false;

    for( int iField = 0; !bIsEurostatTSV && iField < nFieldCount; iField++ )
    {
        char *pszFieldName = nullptr;
        char szFieldNameBuffer[100];

        if( bHasFieldNames )
        {
            pszFieldName = papszTokens[iField];

            // Trim white space.
            while( *pszFieldName == ' ' )
                pszFieldName++;

            while( pszFieldName[0] != '\0' &&
                   pszFieldName[strlen(pszFieldName) - 1] == ' ' )
                pszFieldName[strlen(pszFieldName) - 1] = '\0';

            if( *pszFieldName == '\0' )
                pszFieldName = nullptr;
        }

        if( pszFieldName == nullptr )
        {
            // Re-read single column CSV files that have a trailing comma
            // in the header line.
            if( iField == 1 && nFieldCount == 2 && papszTokens[1][0] == '\0' )
            {
                nCSVFieldCount = 1;
                nFieldCount = 1;
                break;
            }
            pszFieldName = szFieldNameBuffer;
            snprintf(szFieldNameBuffer, sizeof(szFieldNameBuffer),
                     "field_%d", iField + 1);
        }

        OGRFieldDefn oField(pszFieldName, OFTString);
        if( papszFieldTypes != nullptr && iField < CSLCount(papszFieldTypes) )
        {
            if( EQUAL(papszFieldTypes[iField], "WKT") )
            {
                if( bKeepGeomColumns )
                    poFeatureDefn->AddFieldDefn(&oField);

                if( poFeatureDefn->GetGeomFieldCount() == knMAX_GEOM_COLUMNS )
                {
                    if( !bWarnedMaxGeomFields )
                    {
                        CPLError(CE_Warning, CPLE_NotSupported,
                            "A maximum number of %d geometry fields is supported. "
                            "Only the first ones are taken into account.",
                            knMAX_GEOM_COLUMNS);
                        bWarnedMaxGeomFields = true;
                    }
                    continue;
                }

                eGeometryFormat = OGR_CSV_GEOM_AS_WKT;
                panGeomFieldIndex[iField] = poFeatureDefn->GetGeomFieldCount();
                OGRGeomFieldDefn oGeomFieldDefn(oField.GetNameRef(),
                                                wkbUnknown);
                poFeatureDefn->AddGeomFieldDefn(&oGeomFieldDefn);
                continue;
            }
            else if( EQUAL(papszFieldTypes[iField], "CoordX") ||
                     EQUAL(papszFieldTypes[iField], "Point(X)") )
            {
                oField.SetType(OFTReal);
                iLongitudeField = iField;
                osXField = oField.GetNameRef();
                if( bKeepGeomColumns )
                    poFeatureDefn->AddFieldDefn(&oField);
                continue;
            }
            else if( EQUAL(papszFieldTypes[iField], "CoordY") ||
                     EQUAL(papszFieldTypes[iField], "Point(Y)") )
            {
                oField.SetType(OFTReal);
                iLatitudeField = iField;
                osYField = oField.GetNameRef();
                if( bKeepGeomColumns )
                    poFeatureDefn->AddFieldDefn(&oField);
                continue;
            }
            else if( EQUAL(papszFieldTypes[iField], "CoordZ") ||
                     EQUAL(papszFieldTypes[iField], "Point(Z)") )
            {
                oField.SetType(OFTReal);
                iZField = iField;
                osZField = oField.GetNameRef();
                if( bKeepGeomColumns )
                    poFeatureDefn->AddFieldDefn(&oField);
                continue;
            }
            else if( EQUAL(papszFieldTypes[iField], "Integer(Boolean)") )
            {
                oField.SetType(OFTInteger);
                oField.SetSubType(OFSTBoolean);
                oField.SetWidth(1);
            }
            else if( EQUAL(papszFieldTypes[iField], "Integer(Int16)") )
            {
                oField.SetType(OFTInteger);
                oField.SetSubType(OFSTInt16);
            }
            else if( EQUAL(papszFieldTypes[iField], "Real(Float32)") )
            {
                oField.SetType(OFTReal);
                oField.SetSubType(OFSTFloat32);
            }
            else
            {
                char *pszLeftParenthesis = strchr(papszFieldTypes[iField], '(');
                if( pszLeftParenthesis &&
                    pszLeftParenthesis != papszFieldTypes[iField] &&
                    pszLeftParenthesis[1] >= '0' &&
                    pszLeftParenthesis[1] <= '9' )
                {
                    char *pszDot = strchr(pszLeftParenthesis, '.');
                    if( pszDot )
                        *pszDot = 0;
                    *pszLeftParenthesis = 0;

                    if( pszLeftParenthesis[-1] == ' ' )
                        pszLeftParenthesis[-1] = 0;

                    const int nWidth = atoi(pszLeftParenthesis + 1);
                    const int nPrecision = pszDot ? atoi(pszDot + 1) : 0;

                    oField.SetWidth(nWidth);
                    oField.SetPrecision(nPrecision);
                }

                if( EQUAL(papszFieldTypes[iField], "Integer") )
                    oField.SetType(OFTInteger);
                else if (EQUAL(papszFieldTypes[iField], "Integer64") )
                    oField.SetType(OFTInteger64);
                else if( EQUAL(papszFieldTypes[iField], "Real") )
                    oField.SetType(OFTReal);
                else if( EQUAL(papszFieldTypes[iField], "String") )
                    oField.SetType(OFTString);
                else if( EQUAL(papszFieldTypes[iField], "Date") )
                    oField.SetType(OFTDate);
                else if( EQUAL(papszFieldTypes[iField], "Time") )
                    oField.SetType(OFTTime);
                else if( EQUAL(papszFieldTypes[iField], "DateTime") )
                    oField.SetType(OFTDateTime);
                else if( EQUAL(papszFieldTypes[iField], "JSonStringList") )
                    oField.SetType(OFTStringList);
                else if( EQUAL(papszFieldTypes[iField], "JSonIntegerList") )
                    oField.SetType(OFTIntegerList);
                else if( EQUAL(papszFieldTypes[iField], "JSonInteger64List") )
                    oField.SetType(OFTInteger64List);
                else if( EQUAL(papszFieldTypes[iField], "JSonRealList") )
                    oField.SetType(OFTRealList);
                else
                    CPLError(CE_Warning, CPLE_NotSupported,
                             "Unknown type : %s", papszFieldTypes[iField]);
            }
        }

        if( Matches(oField.GetNameRef(), papszZPossibleNames) )
        {
            oField.SetType(OFTReal);
            iZField = iField;
            osZField = oField.GetNameRef();
            if( !bKeepGeomColumns )
                continue;
        }
        else if( (iNfdcLatitudeS != -1 && iNfdcLongitudeS != -1) ||
                 (iLatitudeField != -1 && iLongitudeField != -1) )
        {
            // Do nothing.
        }
        else if( (EQUAL(oField.GetNameRef(), "WKT") ||
                  STARTS_WITH_CI(oField.GetNameRef(), "_WKT")) &&
                 oField.GetType() == OFTString )
        {
            if( poFeatureDefn->GetGeomFieldCount() == knMAX_GEOM_COLUMNS )
            {
                if( !bWarnedMaxGeomFields )
                {
                    CPLError(CE_Warning, CPLE_NotSupported,
                        "A maximum number of %d geometry fields is supported. "
                        "Only the first ones are taken into account.",
                        knMAX_GEOM_COLUMNS);
                    bWarnedMaxGeomFields = true;
                }
            }
            else
            {
                eGeometryFormat = OGR_CSV_GEOM_AS_WKT;

                panGeomFieldIndex[iField] = poFeatureDefn->GetGeomFieldCount();
                OGRGeomFieldDefn oGeomFieldDefn(
                    EQUAL(pszFieldName, "WKT") ? "" : CPLSPrintf("geom_%s",
                                                                pszFieldName),
                    wkbUnknown );

                // Useful hack for RFC 41 testing.
                const char *pszEPSG = strstr(pszFieldName, "_EPSG_");
                if( pszEPSG != nullptr )
                {
                    const int nEPSGCode = atoi(pszEPSG + strlen("_EPSG_"));
                    OGRSpatialReference *poSRS = new OGRSpatialReference();
                    poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
                    poSRS->importFromEPSG(nEPSGCode);
                    oGeomFieldDefn.SetSpatialRef(poSRS);
                    poSRS->Release();
                }

                if( strstr(pszFieldName, "_POINT") )
                    oGeomFieldDefn.SetType(wkbPoint);
                else if( strstr(pszFieldName, "_LINESTRING") )
                    oGeomFieldDefn.SetType(wkbLineString);
                else if( strstr(pszFieldName, "_POLYGON") )
                    oGeomFieldDefn.SetType(wkbPolygon);
                else if( strstr(pszFieldName, "_MULTIPOINT") )
                    oGeomFieldDefn.SetType(wkbMultiPoint);
                else if( strstr(pszFieldName, "_MULTILINESTRING") )
                    oGeomFieldDefn.SetType(wkbMultiLineString);
                else if( strstr(pszFieldName, "_MULTIPOLYGON") )
                    oGeomFieldDefn.SetType(wkbMultiPolygon);
                else if( strstr(pszFieldName, "_CIRCULARSTRING") )
                    oGeomFieldDefn.SetType(wkbCircularString);
                else if( strstr(pszFieldName, "_COMPOUNDCURVE") )
                    oGeomFieldDefn.SetType(wkbCompoundCurve);
                else if( strstr(pszFieldName, "_CURVEPOLYGON") )
                    oGeomFieldDefn.SetType(wkbCurvePolygon);
                else if( strstr(pszFieldName, "_CURVE") )
                    oGeomFieldDefn.SetType(wkbCurve);
                else if( strstr(pszFieldName, "_SURFACE") )
                    oGeomFieldDefn.SetType(wkbSurface);
                else if( strstr(pszFieldName, "_MULTICURVE") )
                    oGeomFieldDefn.SetType(wkbMultiCurve);
                else if( strstr(pszFieldName, "_MULTISURFACE") )
                    oGeomFieldDefn.SetType(wkbMultiSurface);
                else if( strstr(pszFieldName, "_POLYHEDRALSURFACE") )
                    oGeomFieldDefn.SetType(wkbPolyhedralSurface);
                else if( strstr(pszFieldName, "_TIN") )
                    oGeomFieldDefn.SetType(wkbTIN);
                else if( strstr(pszFieldName, "_TRIANGLE") )
                    oGeomFieldDefn.SetType(wkbTriangle);

                poFeatureDefn->AddGeomFieldDefn(&oGeomFieldDefn);
                if( !bKeepGeomColumns )
                    continue;
            }
        }
        else if( Matches(oField.GetNameRef(), papszGeomPossibleNames) )
        {
            eGeometryFormat = OGR_CSV_GEOM_AS_SOME_GEOM_FORMAT;
            panGeomFieldIndex[iField] = poFeatureDefn->GetGeomFieldCount();
            OGRGeomFieldDefn oGeomFieldDefn(oField.GetNameRef(), wkbUnknown);
            poFeatureDefn->AddGeomFieldDefn(&oGeomFieldDefn);
            if( !bKeepGeomColumns )
                continue;
        }
        else if( Matches(oField.GetNameRef(), papszXPossibleNames) &&
                 poFeatureDefn->GetGeomFieldCount() == 0 )
        {
            oField.SetType(OFTReal);
            iLongitudeField = iField;
            osXField = oField.GetNameRef();
            if( !bKeepGeomColumns )
                continue;
        }
        else if( Matches(oField.GetNameRef(), papszYPossibleNames) &&
                 poFeatureDefn->GetGeomFieldCount() == 0 )
        {
            oField.SetType(OFTReal);
            iLatitudeField = iField;
            osYField = oField.GetNameRef();
            if( !bKeepGeomColumns )
                continue;
        }

        // TODO(schwehr): URL broken.
        // http://www.faa.gov/airports/airport_safety/airportdata_5010/menu/index.cfm
        // specific
        else if( pszNfdcGeomField != nullptr &&
                 EQUALN(oField.GetNameRef(), pszNfdcGeomField,
                        strlen(pszNfdcGeomField)) &&
                 EQUAL(oField.GetNameRef() + strlen(pszNfdcGeomField),
                       "LatitudeS") &&
                 poFeatureDefn->GetGeomFieldCount() == 0 )
        {
            iNfdcLatitudeS = iField;
            if( !bKeepGeomColumns )
                continue;
        }
        else if( pszNfdcGeomField != nullptr &&
                 EQUALN(oField.GetNameRef(), pszNfdcGeomField,
                        strlen(pszNfdcGeomField)) &&
                 EQUAL(oField.GetNameRef() + strlen(pszNfdcGeomField),
                       "LongitudeS") &&
                 poFeatureDefn->GetGeomFieldCount() == 0 )
        {
            iNfdcLongitudeS = iField;
            if( !bKeepGeomColumns )
                continue;
        }
        // GNIS specific.
        else if( pszGeonamesGeomFieldPrefix != nullptr &&
                 EQUALN(oField.GetNameRef(), pszGeonamesGeomFieldPrefix,
                        strlen(pszGeonamesGeomFieldPrefix)) &&
                 (EQUAL(oField.GetNameRef() +
                            strlen(pszGeonamesGeomFieldPrefix),
                        "_LAT_DEC") ||
                  EQUAL(oField.GetNameRef() +
                            strlen(pszGeonamesGeomFieldPrefix),
                        "_LATITUDE_DEC") ||
                  EQUAL(oField.GetNameRef() +
                            strlen(pszGeonamesGeomFieldPrefix),
                        "_LATITUDE")) &&
                 poFeatureDefn->GetGeomFieldCount() == 0 )
        {
            oField.SetType(OFTReal);
            iLatitudeField = iField;
            osYField = oField.GetNameRef();
            if( !bKeepGeomColumns )
                continue;
        }
        else if( pszGeonamesGeomFieldPrefix != nullptr &&
                 EQUALN(oField.GetNameRef(), pszGeonamesGeomFieldPrefix,
                        strlen(pszGeonamesGeomFieldPrefix)) &&
                 (EQUAL(oField.GetNameRef() +
                            strlen(pszGeonamesGeomFieldPrefix),
                        "_LONG_DEC") ||
                  EQUAL(oField.GetNameRef() +
                            strlen(pszGeonamesGeomFieldPrefix),
                        "_LONGITUDE_DEC") ||
                  EQUAL(oField.GetNameRef() +
                            strlen(pszGeonamesGeomFieldPrefix),
                        "_LONGITUDE")) &&
                 poFeatureDefn->GetGeomFieldCount() == 0 )
        {
            oField.SetType(OFTReal);
            iLongitudeField = iField;
            osXField = oField.GetNameRef();
            if( !bKeepGeomColumns )
                continue;
        }

        poFeatureDefn->AddFieldDefn(&oField);

        if( bKeepSourceColumns && oField.GetType() != OFTString )
        {
            OGRFieldDefn oFieldOriginal(
                CPLSPrintf("%s_original", oField.GetNameRef()), OFTString);
            poFeatureDefn->AddFieldDefn(&oFieldOriginal);
        }
    }

    if( iNfdcLatitudeS != -1 && iNfdcLongitudeS != -1 )
    {
        bDontHonourStrings = true;
        if( poFeatureDefn->GetGeomFieldCount() == 0 )
        {
            poFeatureDefn->SetGeomType(wkbPoint);
        }
        else
        {
            iNfdcLatitudeS = -1;
            iNfdcLongitudeS = -1;
            iLatitudeField = -1;
            iLongitudeField = -1;
        }
    }
    else if( iLatitudeField != -1 && iLongitudeField != -1 )
    {
        if( poFeatureDefn->GetGeomFieldCount() == 0 )
        {
            poFeatureDefn->SetGeomType(iZField >= 0 ? wkbPoint25D : wkbPoint);
        }
        else
        {
            iNfdcLatitudeS = -1;
            iNfdcLongitudeS = -1;
            iLatitudeField = -1;
            iLongitudeField = -1;
        }
    }

    if( poFeatureDefn->GetGeomFieldCount() > 0 &&
        poFeatureDefn->GetGeomFieldDefn(0)->GetSpatialRef() == nullptr )
    {
        VSILFILE *fpPRJ =
            VSIFOpenL(CPLResetExtension(pszFilename, "prj"), "rb");
        if( fpPRJ != nullptr )
        {
            GByte *pabyRet = nullptr;
            if( VSIIngestFile(fpPRJ, nullptr, &pabyRet, nullptr, 1000000) )
            {
                OGRSpatialReference *poSRS = new OGRSpatialReference();
                poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
                if( poSRS->SetFromUserInput((const char *)pabyRet) ==
                    OGRERR_NONE )
                {
                    poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
                }
                poSRS->Release();
            }
            CPLFree(pabyRet);
            VSIFCloseL(fpPRJ);
        }
    }

    CSLDestroy(papszGeomPossibleNames);
    CSLDestroy(papszXPossibleNames);
    CSLDestroy(papszYPossibleNames);
    CSLDestroy(papszZPossibleNames);

    // Build field definitions for Eurostat TSV files.

    CPLString osSeqDim;
    for( int iField = 0; bIsEurostatTSV && iField < nFieldCount; iField++ )
    {
        if( iField == 0 )
        {
            char **papszDims = CSLTokenizeString2(papszTokens[0], ",\\", 0);
            nEurostatDims = CSLCount(papszDims) - 1;
            for(int iSubField = 0; iSubField < nEurostatDims; iSubField++)
            {
                OGRFieldDefn oField(papszDims[iSubField], OFTString);
                poFeatureDefn->AddFieldDefn(&oField);
            }

            if( nEurostatDims >= 0 )
                osSeqDim = papszDims[nEurostatDims];
            else
                CPLError(CE_Warning, CPLE_AppDefined, "Invalid nEurostatDims");

            CSLDestroy(papszDims);
        }
        else
        {
            if( papszTokens[iField][0] != '\0' &&
                papszTokens[iField][strlen(papszTokens[iField]) - 1] == ' ' )
                papszTokens[iField][strlen(papszTokens[iField]) - 1] = '\0';

            OGRFieldDefn oField(
                CPLSPrintf("%s_%s", osSeqDim.c_str(), papszTokens[iField]),
                OFTReal);
            poFeatureDefn->AddFieldDefn(&oField);

            OGRFieldDefn oField2(
                CPLSPrintf("%s_%s_flag", osSeqDim.c_str(), papszTokens[iField]),
                OFTString);
            poFeatureDefn->AddFieldDefn(&oField2);
        }
    }

    CSLDestroy(papszTokens);
    CSLDestroy(papszFieldTypes);
}

/************************************************************************/
/*                             OGRCSVIsTrue()                           */
/************************************************************************/

static bool OGRCSVIsTrue(const char *pszStr)
{
    return EQUAL(pszStr, "t") || EQUAL(pszStr, "true") || EQUAL(pszStr, "y") ||
           EQUAL(pszStr, "yes") || EQUAL(pszStr, "on");
}

/************************************************************************/
/*                            OGRCSVIsFalse()                           */
/************************************************************************/

static bool OGRCSVIsFalse(const char *pszStr)
{
    return EQUAL(pszStr, "f") || EQUAL(pszStr, "false") || EQUAL(pszStr, "n") ||
           EQUAL(pszStr, "no") || EQUAL(pszStr, "off");
}

/************************************************************************/
/*                        AutodetectFieldTypes()                        */
/************************************************************************/

char **OGRCSVLayer::AutodetectFieldTypes(char **papszOpenOptions,
                                         int nFieldCount)
{
    // Use 1000000 as default maximum distance to be compatible with /vsistdin/
    // caching.
    int nBytes = atoi(CSLFetchNameValueDef(papszOpenOptions,
                                           "AUTODETECT_SIZE_LIMIT", "1000000"));
    if( nBytes == 0 )
    {
        const vsi_l_offset nCurPos = VSIFTellL(fpCSV);
        CPL_IGNORE_RET_VAL(VSIFSeekL(fpCSV, 0, SEEK_END));
        const vsi_l_offset nFileSize = VSIFTellL(fpCSV);
        CPL_IGNORE_RET_VAL(VSIFSeekL(fpCSV, nCurPos, SEEK_SET));
        nBytes = std::min(static_cast<int>(nFileSize),
                          std::numeric_limits<int>::max());
    }
    else if( nBytes < 0 ||
             static_cast<vsi_l_offset>(nBytes) < VSIFTellL(fpCSV) )
    {
        nBytes = 1000000;
    }

    const char *pszAutodetectWidth =
        CSLFetchNameValueDef(papszOpenOptions, "AUTODETECT_WIDTH", "NO");

    const bool bAutodetectWidthForIntOrReal = EQUAL(pszAutodetectWidth, "YES");
    const bool bAutodetectWidth = bAutodetectWidthForIntOrReal ||
                                  EQUAL(pszAutodetectWidth, "STRING_ONLY");

    const bool bQuotedFieldAsString = CPLTestBool(CSLFetchNameValueDef(
        papszOpenOptions, "QUOTED_FIELDS_AS_STRING", "NO"));

    // This will be returned as the result.
    char **papszFieldTypes = nullptr;

    char *pszData = static_cast<char *>(VSI_MALLOC_VERBOSE(nBytes));
    if( pszData != nullptr &&
        static_cast<vsi_l_offset>(nBytes) > VSIFTellL(fpCSV) )
    {
        const int nRequested = nBytes - 1 - static_cast<int>(VSIFTellL(fpCSV));
        const int nRead =
            static_cast<int>(VSIFReadL(pszData, 1, nRequested, fpCSV));
        pszData[nRead] = 0;

        CPLString osTmpMemFile(CPLSPrintf("/vsimem/tmp%p", this));
        VSILFILE *fpMem = VSIFileFromMemBuffer(
            osTmpMemFile, reinterpret_cast<GByte *>(pszData), nRead, FALSE);

        std::vector<OGRFieldType> aeFieldType(nFieldCount);
        std::vector<int> abFieldBoolean(nFieldCount);
        std::vector<int> abFieldSet(nFieldCount);
        std::vector<int> anFieldWidth(nFieldCount);
        std::vector<int> anFieldPrecision(nFieldCount);
        int nStringFieldCount = 0;

        while( !VSIFEofL(fpMem) )
        {
            char **papszTokens =
                OGRCSVReadParseLineL(fpMem, chDelimiter, false,
                                     bQuotedFieldAsString, bMergeDelimiter);
            // Can happen if we just reach EOF while trying to read new bytes.
            if( papszTokens == nullptr )
                break;

            // Ignore last line if it is truncated.
            if( VSIFEofL(fpMem) && nRead == nRequested &&
                pszData[nRead - 1] != 13 && pszData[nRead - 1] != 10 )
            {
                CSLDestroy(papszTokens);
                break;
            }

            for( int iField = 0;
                 iField < nFieldCount && papszTokens[iField] != nullptr; iField++ )
            {
                if( papszTokens[iField][0] == 0 )
                    continue;
                if( chDelimiter == ';' )
                {
                    char *chComma = strchr(papszTokens[iField], ',');
                    if( chComma )
                        *chComma = '.';
                }
                CPLValueType eType = CPLGetValueType(papszTokens[iField]);

                int nFieldWidth = 0;
                int nFieldPrecision = 0;

                if( bAutodetectWidth )
                {
                    nFieldWidth = static_cast<int>(strlen(papszTokens[iField]));
                    if( papszTokens[iField][0] == '"' &&
                        papszTokens[iField][nFieldWidth - 1] == '"' )
                    {
                        nFieldWidth -= 2;
                    }
                    if( eType == CPL_VALUE_REAL &&
                        bAutodetectWidthForIntOrReal )
                    {
                        const char *pszDot = strchr(papszTokens[iField], '.');
                        if( pszDot != nullptr )
                            nFieldPrecision =
                                static_cast<int>(strlen(pszDot + 1));
                    }
                }

                OGRFieldType eOGRFieldType;
                bool bIsBoolean = false;
                if( eType == CPL_VALUE_INTEGER )
                {
                    GIntBig nVal = CPLAtoGIntBig(papszTokens[iField]);
                    if( !CPL_INT64_FITS_ON_INT32(nVal) )
                        eOGRFieldType = OFTInteger64;
                    else
                        eOGRFieldType = OFTInteger;
                }
                else if( eType == CPL_VALUE_REAL )
                {
                    eOGRFieldType = OFTReal;
                }
                else if( abFieldSet[iField] &&
                         aeFieldType[iField] == OFTString )
                {
                    eOGRFieldType = OFTString;
                    if( abFieldBoolean[iField] )
                    {
                        abFieldBoolean[iField] =
                            OGRCSVIsTrue(papszTokens[iField]) ||
                            OGRCSVIsFalse(papszTokens[iField]);
                    }
                }
                else
                {
                    OGRField sWrkField;
                    CPLPushErrorHandler(CPLQuietErrorHandler);
                    const bool bSuccess = CPL_TO_BOOL(
                        OGRParseDate(papszTokens[iField], &sWrkField, 0));
                    CPLPopErrorHandler();
                    CPLErrorReset();
                    if( bSuccess )
                    {
                        const bool bHasDate =
                            strchr(papszTokens[iField], '/') != nullptr ||
                            strchr(papszTokens[iField], '-') != nullptr;
                        const bool bHasTime =
                            strchr(papszTokens[iField], ':') != nullptr;
                        if( bHasDate && bHasTime )
                            eOGRFieldType = OFTDateTime;
                        else if( bHasDate )
                            eOGRFieldType = OFTDate;
                        else
                            eOGRFieldType = OFTTime;
                    }
                    else
                    {
                        eOGRFieldType = OFTString;
                        bIsBoolean = OGRCSVIsTrue(papszTokens[iField]) ||
                                     OGRCSVIsFalse(papszTokens[iField]);
                    }
                }

                if( !abFieldSet[iField] )
                {
                    aeFieldType[iField] = eOGRFieldType;
                    abFieldSet[iField] = TRUE;
                    abFieldBoolean[iField] = bIsBoolean;
                    if( eOGRFieldType == OFTString && !bIsBoolean )
                        nStringFieldCount++;
                }
                else if( aeFieldType[iField] != eOGRFieldType )
                {
                    // Promotion rules.
                    if( aeFieldType[iField] == OFTInteger )
                    {
                        if( eOGRFieldType == OFTInteger64 ||
                            eOGRFieldType == OFTReal )
                            aeFieldType[iField] = eOGRFieldType;
                        else
                        {
                            aeFieldType[iField] = OFTString;
                            nStringFieldCount++;
                        }
                    }
                    else if( aeFieldType[iField] == OFTInteger64 )
                    {
                        if( eOGRFieldType == OFTReal )
                            aeFieldType[iField] = eOGRFieldType;
                        else if( eOGRFieldType != OFTInteger )
                        {
                            aeFieldType[iField] = OFTString;
                            nStringFieldCount++;
                        }
                    }
                    else if( aeFieldType[iField] == OFTReal )
                    {
                        if( eOGRFieldType != OFTInteger &&
                            eOGRFieldType != OFTInteger64 &&
                            eOGRFieldType != OFTReal )
                        {
                            aeFieldType[iField] = OFTString;
                            nStringFieldCount++;
                        }
                    }
                    else if( aeFieldType[iField] == OFTDate )
                    {
                        if( eOGRFieldType == OFTDateTime )
                            aeFieldType[iField] = OFTDateTime;
                        else
                        {
                            aeFieldType[iField] = OFTString;
                            nStringFieldCount++;
                        }
                    }
                    else if( aeFieldType[iField] == OFTDateTime )
                    {
                        if( eOGRFieldType != OFTDate &&
                            eOGRFieldType != OFTDateTime )
                        {
                            aeFieldType[iField] = OFTString;
                            nStringFieldCount++;
                        }
                    }
                    else if( aeFieldType[iField] == OFTTime )
                    {
                        aeFieldType[iField] = OFTString;
                        nStringFieldCount++;
                    }
                }

                if( nFieldWidth > anFieldWidth[iField] )
                    anFieldWidth[iField] = nFieldWidth;
                if( nFieldPrecision > anFieldPrecision[iField] )
                    anFieldPrecision[iField] = nFieldPrecision;
            }

            CSLDestroy(papszTokens);

            // If all fields are String and we don't need to compute width,
            // just stop auto-detection now.
            if( nStringFieldCount == nFieldCount && bAutodetectWidth )
                break;
        }

        papszFieldTypes =
            static_cast<char **>(CPLCalloc(nFieldCount + 1, sizeof(char *)));
        for( int iField = 0; iField < nFieldCount; iField++ )
        {
            CPLString osFieldType;
            if( !abFieldSet[iField] )
                osFieldType = "String";
            else if( aeFieldType[iField] == OFTInteger )
                osFieldType = "Integer";
            else if( aeFieldType[iField] == OFTInteger64 )
                osFieldType = "Integer64";
            else if( aeFieldType[iField] == OFTReal )
                osFieldType = "Real";
            else if( aeFieldType[iField] == OFTDateTime  )
                osFieldType = "DateTime";
            else if( aeFieldType[iField] == OFTDate  )
                osFieldType = "Date";
            else if( aeFieldType[iField] == OFTTime  )
                osFieldType = "Time";
            else if( aeFieldType[iField] == OFTStringList  )
                osFieldType = "JSonStringList";
            else if( aeFieldType[iField] == OFTIntegerList  )
                osFieldType = "JSonIntegerList";
            else if( aeFieldType[iField] == OFTInteger64List  )
                osFieldType = "JSonInteger64List";
            else if( aeFieldType[iField] == OFTRealList  )
                osFieldType = "JSonRealList";
            else if( abFieldBoolean[iField] )
                osFieldType = "Integer(Boolean)";
            else
                osFieldType = "String";

            if( !abFieldBoolean[iField] )
            {
                if( anFieldWidth[iField] > 0 &&
                    (aeFieldType[iField] == OFTString ||
                    (bAutodetectWidthForIntOrReal &&
                     (aeFieldType[iField] == OFTInteger ||
                      aeFieldType[iField] == OFTInteger64))) )
                {
                    osFieldType += CPLSPrintf(" (%d)", anFieldWidth[iField]);
                }
                else if( anFieldWidth[iField] > 0 &&
                         bAutodetectWidthForIntOrReal &&
                         aeFieldType[iField] == OFTReal )
                {
                    osFieldType += CPLSPrintf(" (%d.%d)", anFieldWidth[iField],
                                              anFieldPrecision[iField]);
                }
            }

            papszFieldTypes[iField] = CPLStrdup(osFieldType);
        }

        VSIFCloseL(fpMem);
        VSIUnlink(osTmpMemFile);
    }
    VSIFree(pszData);

    ResetReading();

    return papszFieldTypes;
}

/************************************************************************/
/*                            ~OGRCSVLayer()                            */
/************************************************************************/

OGRCSVLayer::~OGRCSVLayer()

{
    if( m_nFeaturesRead > 0 )
    {
        CPLDebug("CSV", "%d features read on layer '%s'.",
                 static_cast<int>(m_nFeaturesRead),
                 poFeatureDefn->GetName());
    }

    // Make sure the header file is written even if no features are written.
    if( bNew && bInWriteMode )
        WriteHeader();

    CPLFree(panGeomFieldIndex);

    poFeatureDefn->Release();
    CPLFree(pszFilename);

    if( fpCSV )
        VSIFCloseL(fpCSV);
}

/************************************************************************/
/*                            ResetReading()                            */
/************************************************************************/

void OGRCSVLayer::ResetReading()

{
    if( fpCSV )
        VSIRewindL(fpCSV);

    if( bHasFieldNames )
        CSLDestroy(
            OGRCSVReadParseLineL(fpCSV, chDelimiter, bDontHonourStrings));

    bNeedRewindBeforeRead = false;

    nNextFID = 1;
}

/************************************************************************/
/*                        GetNextLineTokens()                           */
/************************************************************************/

char **OGRCSVLayer::GetNextLineTokens()
{
    while( true )
    {
        // Read the CSV record.
        char **papszTokens = OGRCSVReadParseLineL(
            fpCSV, chDelimiter, bDontHonourStrings, false, bMergeDelimiter);

        if( papszTokens == nullptr )
            return nullptr;

        if( papszTokens[0] != nullptr )
            return papszTokens;

        CSLDestroy(papszTokens);
    }
}

/************************************************************************/
/*                             GetFeature()                             */
/************************************************************************/

OGRFeature *OGRCSVLayer::GetFeature(GIntBig nFID)
{
    if( nFID < 1 || fpCSV == nullptr )
        return nullptr;
    if( nFID < nNextFID || bNeedRewindBeforeRead )
        ResetReading();
    while( nNextFID < nFID )
    {
        char **papszTokens = GetNextLineTokens();
        if( papszTokens == nullptr )
            return nullptr;
        CSLDestroy(papszTokens);
        nNextFID++;
    }
    return GetNextUnfilteredFeature();
}

/************************************************************************/
/*                      GetNextUnfilteredFeature()                      */
/************************************************************************/

OGRFeature *OGRCSVLayer::GetNextUnfilteredFeature()

{
    if( fpCSV == nullptr )
        return nullptr;

    // Read the CSV record.
    char **papszTokens = GetNextLineTokens();
    if( papszTokens == nullptr )
        return nullptr;

    // Create the OGR feature.
    OGRFeature *poFeature = new OGRFeature(poFeatureDefn);

    // Set attributes for any indicated attribute records.
    int iOGRField = 0;
    const int nAttrCount = std::min(
        CSLCount(papszTokens), nCSVFieldCount + (bHiddenWKTColumn ? 1 : 0));
    CPLValueType eType;

    for( int iAttr = 0; !bIsEurostatTSV && iAttr < nAttrCount; iAttr++ )
    {
        if( (iAttr == iLongitudeField || iAttr == iLatitudeField ||
             iAttr == iZField) &&
            !bKeepGeomColumns )
        {
            continue;
        }
        int iGeom = 0;
        if( bHiddenWKTColumn )
        {
            if( iAttr != 0 )
                iGeom = panGeomFieldIndex[iAttr - 1];
        }
        else
        {
            iGeom = panGeomFieldIndex[iAttr];
        }
        if( iGeom >= 0 )
        {
            if( papszTokens[iAttr][0] != '\0' &&
                !(poFeatureDefn->GetGeomFieldDefn(iGeom)->IsIgnored()) )
            {
                const char *pszStr = papszTokens[iAttr];
                while( *pszStr == ' ' )
                    pszStr++;
                OGRGeometry *poGeom = nullptr;

                CPLPushErrorHandler(CPLQuietErrorHandler);
                if( OGRGeometryFactory::createFromWkt(pszStr, nullptr, &poGeom) ==
                    OGRERR_NONE )
                {
                    poGeom->assignSpatialReference(
                        poFeatureDefn->GetGeomFieldDefn(iGeom)
                            ->GetSpatialRef());
                    poFeature->SetGeomFieldDirectly(iGeom, poGeom);
                }
                else if( *pszStr == '{' &&
                         (poGeom = reinterpret_cast<OGRGeometry *>(
                              OGR_G_CreateGeometryFromJson(pszStr))) != nullptr )
                {
                    poFeature->SetGeomFieldDirectly(iGeom, poGeom);
                }
                else if( ((*pszStr >= '0' && *pszStr <= '9') ||
                          (*pszStr >= 'a' && *pszStr <= 'z') ||
                          (*pszStr >= 'A' && *pszStr <= 'Z')) &&
                         (poGeom = OGRGeometryFromHexEWKB(pszStr, nullptr,
                                                          FALSE)) != nullptr )
                {
                    poFeature->SetGeomFieldDirectly(iGeom, poGeom);
                }
                CPLPopErrorHandler();
            }
            if( !bKeepGeomColumns || (iAttr == 0 && bHiddenWKTColumn) )
                continue;
        }

        OGRFieldDefn *poFieldDefn = poFeatureDefn->GetFieldDefn(iOGRField);
        const OGRFieldType eFieldType = poFieldDefn->GetType();
        const OGRFieldSubType eFieldSubType = poFieldDefn->GetSubType();
        if( eFieldType == OFTInteger && eFieldSubType == OFSTBoolean )
        {
            if( papszTokens[iAttr][0] != '\0' && !poFieldDefn->IsIgnored() )
            {
                if( OGRCSVIsTrue(papszTokens[iAttr]) ||
                    strcmp(papszTokens[iAttr], "1") == 0 )
                {
                    poFeature->SetField(iOGRField, 1);
                }
                else if( OGRCSVIsFalse(papszTokens[iAttr]) ||
                         strcmp(papszTokens[iAttr], "0") == 0 )
                {
                    poFeature->SetField(iOGRField, 0);
                }
                else if( !bWarningBadTypeOrWidth )
                {
                    bWarningBadTypeOrWidth = true;
                    CPLError(
                        CE_Warning, CPLE_AppDefined,
                        "Invalid value type found in record %d for field %s. "
                        "This warning will no longer be emitted",
                        nNextFID, poFieldDefn->GetNameRef());
                }
            }
        }
        else if( eFieldType == OFTReal || eFieldType == OFTInteger ||
                 eFieldType == OFTInteger64 )
        {
            if( papszTokens[iAttr][0] != '\0' && !poFieldDefn->IsIgnored() )
            {
                if( chDelimiter == ';' && eFieldType == OFTReal )
                {
                    char *chComma = strchr(papszTokens[iAttr], ',');
                    if( chComma )
                        *chComma = '.';
                }
                eType = CPLGetValueType(papszTokens[iAttr]);
                if( eType == CPL_VALUE_INTEGER || eType == CPL_VALUE_REAL )
                {
                    poFeature->SetField(iOGRField, papszTokens[iAttr]);
                    if( !bWarningBadTypeOrWidth &&
                        (eFieldType == OFTInteger ||
                         eFieldType == OFTInteger64) &&
                        eType == CPL_VALUE_REAL )
                    {
                        bWarningBadTypeOrWidth = true;
                        CPLError(CE_Warning, CPLE_AppDefined,
                                 "Invalid value type found in record %d for "
                                 "field %s. "
                                 "This warning will no longer be emitted",
                                 nNextFID, poFieldDefn->GetNameRef());
                    }
                    else if( !bWarningBadTypeOrWidth &&
                             poFieldDefn->GetWidth() > 0 &&
                             static_cast<int>(strlen(papszTokens[iAttr])) >
                                 poFieldDefn->GetWidth() )
                    {
                        bWarningBadTypeOrWidth = true;
                        CPLError(CE_Warning, CPLE_AppDefined,
                                 "Value with a width greater than field width "
                                 "found in record %d for field %s. "
                                 "This warning will no longer be emitted",
                                 nNextFID, poFieldDefn->GetNameRef());
                    }
                    else if( !bWarningBadTypeOrWidth &&
                             eType == CPL_VALUE_REAL &&
                             poFieldDefn->GetWidth() > 0)
                    {
                        const char *pszDot = strchr(papszTokens[iAttr], '.');
                        const int nPrecision =
                            pszDot != nullptr
                                ? static_cast<int>(strlen(pszDot + 1))
                                : 0;
                        if( nPrecision > poFieldDefn->GetPrecision() )
                        {
                            bWarningBadTypeOrWidth = true;
                            CPLError(CE_Warning, CPLE_AppDefined,
                                     "Value with a precision greater than "
                                     "field precision found in record %d for "
                                     "field %s. "
                                     "This warning will no longer be emitted",
                                     nNextFID, poFieldDefn->GetNameRef());
                        }
                    }
                }
                else
                {
                    if( !bWarningBadTypeOrWidth )
                    {
                        bWarningBadTypeOrWidth = true;
                        CPLError(
                            CE_Warning, CPLE_AppDefined,
                            "Invalid value type found in record %d for field "
                            "%s. This warning will no longer be emitted.",
                            nNextFID, poFieldDefn->GetNameRef());
                    }
                }
            }
        }
        else if( eFieldType != OFTString )
        {
            if( papszTokens[iAttr][0] != '\0' && !poFieldDefn->IsIgnored() )
            {
                poFeature->SetField(iOGRField, papszTokens[iAttr]);
                if( !bWarningBadTypeOrWidth &&
                    !poFeature->IsFieldSetAndNotNull(iOGRField) )
                {
                    bWarningBadTypeOrWidth = true;
                    CPLError(
                        CE_Warning, CPLE_AppDefined,
                        "Invalid value type found in record %d for field %s. "
                        "This warning will no longer be emitted",
                        nNextFID, poFieldDefn->GetNameRef());
                }
            }
        }
        else if( !poFieldDefn->IsIgnored() )
        {
            if( bEmptyStringNull && papszTokens[iAttr][0] == '\0' )
            {
                poFeature->SetFieldNull(iOGRField);
            }
            else
            {
                poFeature->SetField(iOGRField, papszTokens[iAttr]);
                if( !bWarningBadTypeOrWidth && poFieldDefn->GetWidth() > 0 &&
                    static_cast<int>(strlen(papszTokens[iAttr])) >
                        poFieldDefn->GetWidth() )
                {
                    bWarningBadTypeOrWidth = true;
                    CPLError(CE_Warning, CPLE_AppDefined,
                             "Value with a width greater than field width "
                             "found in record %d for field %s. "
                             "This warning will no longer be emitted",
                             nNextFID, poFieldDefn->GetNameRef());
                }
            }
        }

        if( bKeepSourceColumns && eFieldType != OFTString )
        {
            iOGRField++;
            if( papszTokens[iAttr][0] != '\0' &&
                !poFeatureDefn->GetFieldDefn(iOGRField)->IsIgnored() )
            {
                poFeature->SetField(iOGRField, papszTokens[iAttr]);
            }
        }

        iOGRField++;
    }

    // Eurostat TSV files.

    for( int iAttr = 0; bIsEurostatTSV && iAttr < nAttrCount; iAttr++ )
    {
        if( iAttr == 0 )
        {
            char **papszDims = CSLTokenizeString2(papszTokens[0], ",", 0);
            if( CSLCount(papszDims) != nEurostatDims )
            {
                CSLDestroy(papszDims);
                break;
            }
            for( int iSubAttr = 0; iSubAttr < nEurostatDims; iSubAttr++ )
            {
                if( !poFeatureDefn->GetFieldDefn(iSubAttr)->IsIgnored() )
                    poFeature->SetField(iSubAttr, papszDims[iSubAttr]);
            }
            CSLDestroy(papszDims);
        }
        else
        {
            char **papszVals = CSLTokenizeString2(papszTokens[iAttr], " ", 0);
            eType = CPLGetValueType(papszVals[0]);
            if( (papszVals[0] && papszVals[0][0] != '\0') &&
                (eType == CPL_VALUE_INTEGER || eType == CPL_VALUE_REAL) )
            {
                if( !poFeatureDefn->GetFieldDefn(nEurostatDims + 2 * (iAttr - 1))
                         ->IsIgnored() )
                    poFeature->SetField(nEurostatDims + 2 * (iAttr - 1),
                                        papszVals[0]);
            }
            if( CSLCount(papszVals) == 2 )
            {
                if( !poFeatureDefn
                         ->GetFieldDefn(nEurostatDims + 2 * (iAttr - 1) + 1)
                         ->IsIgnored() )
                    poFeature->SetField(nEurostatDims + 2 * (iAttr - 1) + 1,
                                        papszVals[1]);
            }
            CSLDestroy(papszVals);
        }
    }

    // http://www.faa.gov/airports/airport_safety/airportdata_5010/menu/index.cfm
    // specific

    if( iNfdcLatitudeS != -1 &&
        iNfdcLongitudeS != -1 &&
        nAttrCount > iNfdcLatitudeS &&
        nAttrCount > iNfdcLongitudeS  &&
        papszTokens[iNfdcLongitudeS][0] != 0 &&
        papszTokens[iNfdcLatitudeS][0] != 0 )
    {
        const double dfLon =
            CPLAtof(papszTokens[iNfdcLongitudeS]) / 3600.0 *
            (strchr(papszTokens[iNfdcLongitudeS], 'W') ? -1.0 : 1.0);
        const double dfLat =
            CPLAtof(papszTokens[iNfdcLatitudeS]) / 3600.0 *
            (strchr(papszTokens[iNfdcLatitudeS], 'S') ? -1.0 : 1.0);
        if( !poFeatureDefn->GetGeomFieldDefn(0)->IsIgnored() )
            poFeature->SetGeometryDirectly(new OGRPoint(dfLon, dfLat));
    }

    // GNIS specific.
    else if( iLatitudeField != -1 &&
             iLongitudeField != -1 &&
             nAttrCount > iLatitudeField &&
             nAttrCount > iLongitudeField  &&
             papszTokens[iLongitudeField][0] != 0 &&
             papszTokens[iLatitudeField][0] != 0 )
    {
        // Some records have dummy 0,0 value.
        if( papszTokens[iLongitudeField][0] != DIGIT_ZERO ||
            papszTokens[iLongitudeField][1] != '\0' ||
            papszTokens[iLatitudeField][0] != DIGIT_ZERO ||
            papszTokens[iLatitudeField][1] != '\0' )
        {
            const double dfLon = CPLAtof(papszTokens[iLongitudeField]);
            const double dfLat = CPLAtof(papszTokens[iLatitudeField]);
            if( !poFeatureDefn->GetGeomFieldDefn(0)->IsIgnored() )
            {
                if( iZField != -1 && nAttrCount > iZField &&
                    papszTokens[iZField][0] != 0 )
                    poFeature->SetGeometryDirectly(new OGRPoint(
                        dfLon, dfLat, CPLAtof(papszTokens[iZField])));
                else
                    poFeature->SetGeometryDirectly(new OGRPoint(dfLon, dfLat));
            }
        }
    }

    CSLDestroy(papszTokens);

    // Translate the record id.
    poFeature->SetFID(nNextFID++);

    m_nFeaturesRead++;

    return poFeature;
}

/************************************************************************/
/*                           GetNextFeature()                           */
/************************************************************************/

OGRFeature *OGRCSVLayer::GetNextFeature()

{
    if( bNeedRewindBeforeRead )
        ResetReading();

    // Read features till we find one that satisfies our current
    // spatial criteria.
    while( true )
    {
        OGRFeature *poFeature = GetNextUnfilteredFeature();
        if( poFeature == nullptr )
            return nullptr;

        if( (m_poFilterGeom == nullptr ||
             FilterGeometry(poFeature->GetGeomFieldRef(m_iGeomFieldFilter))) &&
            (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)) )
            return poFeature;

        delete poFeature;
    }
}

/************************************************************************/
/*                           TestCapability()                           */
/************************************************************************/

int OGRCSVLayer::TestCapability( const char *pszCap )

{
    if( EQUAL(pszCap, OLCSequentialWrite) )
        return bInWriteMode && !bKeepSourceColumns && bKeepGeomColumns;
    else if( EQUAL(pszCap, OLCCreateField) )
        return bNew && !bHasFieldNames;
    else if( EQUAL(pszCap, OLCCreateGeomField) )
        return bNew && !bHasFieldNames &&
               eGeometryFormat == OGR_CSV_GEOM_AS_WKT;
    else if( EQUAL(pszCap, OLCIgnoreFields) )
        return TRUE;
    else if( EQUAL(pszCap, OLCCurveGeometries) )
        return TRUE;
    else if( EQUAL(pszCap, OLCMeasuredGeometries) )
        return TRUE;
    else
        return FALSE;
}

/************************************************************************/
/*                          PreCreateField()                            */
/************************************************************************/

OGRCSVCreateFieldAction
OGRCSVLayer::PreCreateField( OGRFeatureDefn *poFeatureDefn,
                             const std::set<CPLString>& oSetFields,
                             OGRFieldDefn *poNewField, int bApproxOK )
{
    // Does this duplicate an existing field?
    if( oSetFields.find(CPLString(poNewField->GetNameRef()).toupper()) != oSetFields.end() )
    {
        if( poFeatureDefn->GetGeomFieldIndex(poNewField->GetNameRef()) >= 0 ||
            poFeatureDefn->GetGeomFieldIndex(
                CPLSPrintf("geom_%s", poNewField->GetNameRef())) >= 0 )
        {
            return CREATE_FIELD_DO_NOTHING;
        }
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Attempt to create field %s, "
                 "but a field with this name already exists.",
                 poNewField->GetNameRef());

        return CREATE_FIELD_ERROR;
    }

    // Is this a legal field type for CSV?
    switch( poNewField->GetType() )
    {
    case OFTInteger:
    case OFTInteger64:
    case OFTReal:
    case OFTString:
    case OFTIntegerList:
    case OFTInteger64List:
    case OFTRealList:
    case OFTStringList:
    case OFTTime:
    case OFTDate:
    case OFTDateTime:
        // These types are OK.
        break;

    default:
        if( bApproxOK )
        {
            CPLError(
                CE_Warning, CPLE_AppDefined,
                "Attempt to create field of type %s, but this is not supported "
                "for .csv files.  Just treating as a plain string.",
                poNewField->GetFieldTypeName(poNewField->GetType()));
        }
        else
        {
            CPLError(
                CE_Failure, CPLE_AppDefined,
                "Attempt to create field of type %s, but this is not supported "
                "for .csv files.",
                poNewField->GetFieldTypeName(poNewField->GetType()));
            return CREATE_FIELD_ERROR;
        }
    }
    return CREATE_FIELD_PROCEED;
}

/************************************************************************/
/*                            CreateField()                             */
/************************************************************************/

OGRErr OGRCSVLayer::CreateField( OGRFieldDefn *poNewField, int bApproxOK )

{
    // If we have already written our field names, then we are not
    // allowed to add new fields.
    if( !TestCapability(OLCCreateField) )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Unable to create new fields after first feature written.");
        return OGRERR_FAILURE;
    }

    if( nCSVFieldCount >= 10000 )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Limiting to 10000 fields");
        return OGRERR_FAILURE;
    }

    if( m_oSetFields.empty() )
    {
        for( int i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
        {
            m_oSetFields.insert(CPLString(
                poFeatureDefn->GetFieldDefn(i)->GetNameRef()).toupper());
        }
    }

    const OGRCSVCreateFieldAction eAction =
        PreCreateField(poFeatureDefn, m_oSetFields, poNewField, bApproxOK);
    if( eAction == CREATE_FIELD_DO_NOTHING )
        return OGRERR_NONE;
    if( eAction == CREATE_FIELD_ERROR )
        return OGRERR_FAILURE;

    // Seems ok, add to field list.
    poFeatureDefn->AddFieldDefn(poNewField);
    nCSVFieldCount++;
    m_oSetFields.insert(CPLString(poNewField->GetNameRef()).toupper());

    panGeomFieldIndex = static_cast<int *>(CPLRealloc(
        panGeomFieldIndex, sizeof(int) * poFeatureDefn->GetFieldCount()));
    panGeomFieldIndex[poFeatureDefn->GetFieldCount() - 1] = -1;

    return OGRERR_NONE;
}

/************************************************************************/
/*                          CreateGeomField()                           */
/************************************************************************/

OGRErr OGRCSVLayer::CreateGeomField( OGRGeomFieldDefn *poGeomField,
                                     int /* bApproxOK */ )
{
    if( !TestCapability(OLCCreateGeomField) )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Unable to create new fields after first feature written.");
        return OGRERR_FAILURE;
    }

    // Does this duplicate an existing field?
    if( poFeatureDefn->GetGeomFieldIndex(poGeomField->GetNameRef()) >= 0 )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Attempt to create geom field %s, "
                 "but a field with this name already exists.",
                 poGeomField->GetNameRef());

        return OGRERR_FAILURE;
    }
    OGRGeomFieldDefn oGeomField(poGeomField);
    if( oGeomField.GetSpatialRef() )
    {
        oGeomField.GetSpatialRef()->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    }
    poFeatureDefn->AddGeomFieldDefn(&oGeomField);

    const char *pszName = poGeomField->GetNameRef();
    if( EQUAL(pszName, ""))
    {
        const int nIdx = poFeatureDefn->GetFieldIndex("WKT");
        if( nIdx >= 0 )
        {
            panGeomFieldIndex[nIdx] = poFeatureDefn->GetGeomFieldCount() - 1;
            return OGRERR_NONE;
        }
        pszName = "WKT";
    }
    if( STARTS_WITH_CI(pszName, "geom_") && strlen(pszName) >= strlen("geom_") )
        pszName += strlen("geom_");
    if( !EQUAL(pszName, "WKT") && !STARTS_WITH_CI(pszName, "_WKT") )
        pszName = CPLSPrintf("_WKT%s", pszName);

    OGRFieldDefn oRegularFieldDefn(pszName, OFTString);
    poFeatureDefn->AddFieldDefn(&oRegularFieldDefn);
    nCSVFieldCount++;

    panGeomFieldIndex = static_cast<int *>(CPLRealloc(
        panGeomFieldIndex, sizeof(int) * poFeatureDefn->GetFieldCount()));
    panGeomFieldIndex[poFeatureDefn->GetFieldCount() - 1] =
        poFeatureDefn->GetGeomFieldCount() - 1;

    return OGRERR_NONE;
}

/************************************************************************/
/*                            WriteHeader()                             */
/*                                                                      */
/*      Write the header, and possibly the .csvt file if they           */
/*      haven't already been written.                                   */
/************************************************************************/

OGRErr OGRCSVLayer::WriteHeader()
{
    if( !bNew )
        return OGRERR_NONE;

    // Write field names if we haven't written them yet.
    // Write .csvt file if needed.
    bNew = false;
    bHasFieldNames = true;
    bool bOK = true;

    for( int iFile = 0; iFile < (bCreateCSVT ? 2 : 1); iFile++ )
    {
        VSILFILE *fpCSVT = nullptr;
        if( bCreateCSVT && iFile == 0 )
        {
            char *pszDirName = CPLStrdup(CPLGetDirname(pszFilename));
            char *pszBaseName = CPLStrdup(CPLGetBasename(pszFilename));
            fpCSVT = VSIFOpenL(
                CPLFormFilename(pszDirName, pszBaseName, ".csvt"), "wb");
            CPLFree(pszDirName);
            CPLFree(pszBaseName);
        }
        else
        {
            if( STARTS_WITH(pszFilename, "/vsistdout/") ||
                STARTS_WITH(pszFilename, "/vsizip/") )
                fpCSV = VSIFOpenL(pszFilename, "wb");
            else
                fpCSV = VSIFOpenL(pszFilename, "w+b");

            if( fpCSV == nullptr )
            {
                CPLError(CE_Failure, CPLE_OpenFailed,
                         "Failed to create %s:\n%s",
                         pszFilename, VSIStrerror(errno));
                return OGRERR_FAILURE;
            }
        }

        if( bWriteBOM && fpCSV )
        {
            bOK &= VSIFWriteL("\xEF\xBB\xBF", 1, 3, fpCSV) > 0;
        }

        if( eGeometryFormat == OGR_CSV_GEOM_AS_XYZ )
        {
            if( fpCSV )
                bOK &=
                    VSIFPrintfL(fpCSV, "X%cY%cZ", chDelimiter, chDelimiter) > 0;
            if( fpCSVT )
                bOK &= VSIFPrintfL(fpCSVT, "%s", "CoordX,CoordY,Real") > 0;
            if( poFeatureDefn->GetFieldCount() > 0 )
            {
                if( fpCSV )
                    bOK &= VSIFPrintfL(fpCSV, "%c", chDelimiter) > 0;
                if( fpCSVT )
                    bOK &= VSIFPrintfL(fpCSVT, "%s", ",") > 0;
            }
        }
        else if( eGeometryFormat == OGR_CSV_GEOM_AS_XY )
        {
            if( fpCSV )
                bOK &= VSIFPrintfL(fpCSV, "X%cY", chDelimiter) > 0;
            if( fpCSVT )
                bOK &= VSIFPrintfL(fpCSVT, "%s", "CoordX,CoordY") > 0;
            if( poFeatureDefn->GetFieldCount() > 0 )
            {
                if( fpCSV )
                    bOK &= VSIFPrintfL(fpCSV, "%c", chDelimiter) > 0;
                if( fpCSVT )
                    bOK &= VSIFPrintfL(fpCSVT, "%s", ",") > 0;
            }
        }
        else if( eGeometryFormat == OGR_CSV_GEOM_AS_YX )
        {
            if( fpCSV )
                bOK &= VSIFPrintfL(fpCSV, "Y%cX", chDelimiter) > 0;
            if( fpCSVT )
                bOK &= VSIFPrintfL(fpCSVT, "%s", "CoordY,CoordX") > 0;
            if( poFeatureDefn->GetFieldCount() > 0 )
            {
                if( fpCSV )
                    bOK &= VSIFPrintfL(fpCSV, "%c", chDelimiter) > 0;
                if(fpCSVT)
                    bOK &= VSIFPrintfL(fpCSVT, "%s", ",") > 0;
            }
        }

        if( bHiddenWKTColumn )
        {
            if( fpCSV )
            {
                const char* pszColName =
                    bCreateCSVT ? poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef() : "WKT";
                bOK &=
                    VSIFPrintfL(fpCSV, "%s", pszColName) >= 0;
            }
            if( fpCSVT )
                bOK &= VSIFPrintfL(fpCSVT, "%s", "WKT") > 0;
        }

        for( int iField = 0; iField < poFeatureDefn->GetFieldCount(); iField++ )
        {
            if( iField > 0 || bHiddenWKTColumn )
            {
                if( fpCSV )
                    bOK &= VSIFPrintfL(fpCSV, "%c", chDelimiter) > 0;
                if( fpCSVT )
                    bOK &= VSIFPrintfL(fpCSVT, "%s", ",") > 0;
            }

            char *pszEscaped = CPLEscapeString(
                poFeatureDefn->GetFieldDefn(iField)->GetNameRef(), -1,
                m_eStringQuoting == StringQuoting::ALWAYS ?
                    CPLES_CSV_FORCE_QUOTING : CPLES_CSV);

            if( fpCSV )
            {
                bool bAddDoubleQuote = false;
                if( chDelimiter == ' ' && pszEscaped[0] != '"' &&
                    strchr(pszEscaped, ' ') != nullptr )
                    bAddDoubleQuote = true;
                if( bAddDoubleQuote )
                    bOK &= VSIFWriteL("\"", 1, 1, fpCSV) > 0;
                bOK &= VSIFPrintfL(fpCSV, "%s", pszEscaped) >= 0;
                if( bAddDoubleQuote )
                    bOK &= VSIFWriteL("\"", 1, 1, fpCSV) > 0;
            }
            CPLFree(pszEscaped);

            if( fpCSVT )
            {
                int nWidth = poFeatureDefn->GetFieldDefn(iField)->GetWidth();
                const int nPrecision =
                    poFeatureDefn->GetFieldDefn(iField)->GetPrecision();

                switch( poFeatureDefn->GetFieldDefn(iField)->GetType() )
                {
                case OFTInteger:
                {
                    if( poFeatureDefn->GetFieldDefn(iField)->GetSubType() ==
                        OFSTBoolean )
                    {
                        nWidth = 0;
                        bOK &=
                            VSIFPrintfL(fpCSVT, "%s", "Integer(Boolean)") > 0;
                    }
                    else if( poFeatureDefn->GetFieldDefn(iField)->
                                 GetSubType() == OFSTInt16 )
                    {
                        nWidth = 0;
                        bOK &= VSIFPrintfL(fpCSVT, "%s", "Integer(Int16)") > 0;
                    }
                    else
                    {
                        bOK &= VSIFPrintfL(fpCSVT, "%s", "Integer") > 0;
                    }
                    break;
                }
                case OFTInteger64:
                    bOK &= VSIFPrintfL(fpCSVT, "%s", "Integer64") > 0;
                    break;
                case OFTReal:
                {
                    if( poFeatureDefn->GetFieldDefn(iField)->GetSubType() ==
                        OFSTFloat32 )
                    {
                        nWidth = 0;
                        bOK &= VSIFPrintfL(fpCSVT, "%s", "Real(Float32)") > 0;
                    }
                    else
                    {
                        bOK &= VSIFPrintfL(fpCSVT, "%s", "Real") > 0;
                    }
                    break;
                }
                case OFTDate:
                    bOK &= VSIFPrintfL(fpCSVT, "%s", "Date") > 0;
                    break;
                case OFTTime:
                    bOK &= VSIFPrintfL(fpCSVT, "%s", "Time") > 0;
                    break;
                case OFTDateTime:
                    bOK &= VSIFPrintfL(fpCSVT, "%s", "DateTime") > 0;
                    break;
                case OFTStringList:
                    bOK &= VSIFPrintfL(fpCSVT, "%s", "JSonStringList") > 0;
                    break;
                case OFTIntegerList:
                    bOK &= VSIFPrintfL(fpCSVT, "%s", "JSonIntegerList") > 0;
                    break;
                case OFTInteger64List:
                    bOK &= VSIFPrintfL(fpCSVT, "%s", "JSonInteger64List") > 0;
                    break;
                case OFTRealList:
                    bOK &= VSIFPrintfL(fpCSVT, "%s", "JSonRealList") > 0;
                    break;
                default:
                    bOK &= VSIFPrintfL(fpCSVT, "%s", "String") > 0;
                    break;
                }

                if( nWidth != 0 )
                {
                    if( nPrecision != 0 )
                        bOK &= VSIFPrintfL(fpCSVT, "(%d.%d)", nWidth,
                                           nPrecision) > 0;
                    else
                        bOK &= VSIFPrintfL(fpCSVT, "(%d)", nWidth) > 0;
                }
            }
        }

        // The CSV driver will not recognize single column tables, so add
        // a fake second blank field.
        if( (poFeatureDefn->GetFieldCount() == 1 && !bHiddenWKTColumn) ||
            (poFeatureDefn->GetFieldCount() == 0 && bHiddenWKTColumn) )
        {
            if( fpCSV )
                bOK &= VSIFPrintfL(fpCSV, "%c", chDelimiter) > 0;
        }

        if( bUseCRLF )
        {
            if( fpCSV )
                bOK &= VSIFPutcL(13, fpCSV) > 0;
            if(fpCSVT)
                bOK &= VSIFPutcL(13, fpCSVT) > 0;
        }
        if( fpCSV )
            bOK &= VSIFPutcL('\n', fpCSV) > 0;
        if( fpCSVT )
            bOK &= VSIFPutcL('\n', fpCSVT) > 0;
        if( fpCSVT )
            VSIFCloseL(fpCSVT);
    }

    return (!bOK || fpCSV == nullptr) ? OGRERR_FAILURE : OGRERR_NONE;
}

/************************************************************************/
/*                           ICreateFeature()                            */
/************************************************************************/

OGRErr OGRCSVLayer::ICreateFeature( OGRFeature *poNewFeature )

{
    if( !bInWriteMode )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "The CreateFeature() operation is not permitted on a "
                 "read-only CSV.");
        return OGRERR_FAILURE;
    }

    // If we need rewind, it means that we have just written a feature before
    // so there's no point seeking to the end of the file, as we're already
    // at the end.
    bool bNeedSeekEnd = !bNeedRewindBeforeRead;

    bNeedRewindBeforeRead = true;

    // Write field names if we haven't written them yet.
    // Write .csvt file if needed.
    if( bNew )
    {
        const OGRErr eErr = WriteHeader();
        if( eErr != OGRERR_NONE )
            return eErr;
        bNeedSeekEnd = false;
    }

    if( fpCSV == nullptr )
        return OGRERR_FAILURE;

    bool bRet = true;

    // Make sure we are at the end of the file.
    if( bNeedSeekEnd )
    {
        if( bFirstFeatureAppendedDuringSession )
        {
            // Add a newline character to the end of the file if necessary.
            bFirstFeatureAppendedDuringSession = false;
            bRet &= VSIFSeekL(fpCSV, 0, SEEK_END) >= 0;
            bRet &= VSIFSeekL(fpCSV, VSIFTellL(fpCSV) - 1, SEEK_SET) >= 0;
            char chLast = '\0';
            bRet &= VSIFReadL(&chLast, 1, 1, fpCSV) > 0;
            bRet &= VSIFSeekL(fpCSV, 0, SEEK_END) >= 0;
            if( chLast != '\n' )
            {
                if( bUseCRLF )
                    bRet &= VSIFPutcL(13, fpCSV) != EOF;
                bRet &= VSIFPutcL('\n', fpCSV) != EOF;
            }
        }
        else
        {
            bRet &= VSIFSeekL(fpCSV, 0, SEEK_END) >= 0;
        }
    }

    // Write out the geometry.
    if( eGeometryFormat == OGR_CSV_GEOM_AS_XYZ ||
        eGeometryFormat == OGR_CSV_GEOM_AS_XY ||
        eGeometryFormat == OGR_CSV_GEOM_AS_YX )
    {
        OGRGeometry *poGeom = poNewFeature->GetGeometryRef();
        if( poGeom && wkbFlatten(poGeom->getGeometryType()) == wkbPoint )
        {
            OGRPoint *poPoint = poGeom->toPoint();
            char szBuffer[75] = {};
            if( eGeometryFormat == OGR_CSV_GEOM_AS_XYZ )
                OGRMakeWktCoordinate(szBuffer, poPoint->getX(), poPoint->getY(),
                                     poPoint->getZ(), 3);
            else if( eGeometryFormat == OGR_CSV_GEOM_AS_XY )
                OGRMakeWktCoordinate(szBuffer, poPoint->getX(), poPoint->getY(),
                                     0, 2);
            else
                OGRMakeWktCoordinate(szBuffer, poPoint->getY(), poPoint->getX(),
                                     0, 2);
            char *pc = szBuffer;
            while(*pc != '\0')
            {
                if( *pc == ' ' )
                    *pc = chDelimiter;
                pc++;
            }
            bRet &= VSIFPrintfL(fpCSV, "%s", szBuffer) > 0;
        }
        else
        {
            bRet &= VSIFPrintfL(fpCSV, "%c", chDelimiter) > 0;
            if( eGeometryFormat == OGR_CSV_GEOM_AS_XYZ )
                bRet &= VSIFPrintfL(fpCSV, "%c", chDelimiter) > 0;
        }
        if( poFeatureDefn->GetFieldCount() > 0 )
            bRet &= VSIFPrintfL(fpCSV, "%c", chDelimiter) > 0;
    }

    // Special case to deal with hidden "WKT" geometry column.
    bool bNonEmptyLine = false;

    if( bHiddenWKTColumn )
    {
        char *pszWKT = nullptr;
        OGRGeometry *poGeom = poNewFeature->GetGeomFieldRef(0);
        if( poGeom &&
            poGeom->exportToWkt(&pszWKT, wkbVariantIso) == OGRERR_NONE )
        {
            bNonEmptyLine = true;
            bRet &= VSIFWriteL("\"", 1, 1, fpCSV) > 0;
            bRet &= VSIFWriteL(pszWKT, strlen(pszWKT), 1, fpCSV) > 0;
            bRet &= VSIFWriteL("\"", 1, 1, fpCSV) > 0;
        }
        CPLFree(pszWKT);
    }

    // Write out all the field values.
    for( int iField = 0; iField < poFeatureDefn->GetFieldCount(); iField++ )
    {
        char *pszEscaped = nullptr;

        if( iField > 0 || bHiddenWKTColumn )
            bRet &= VSIFPrintfL(fpCSV, "%c", chDelimiter) > 0;

        if( eGeometryFormat == OGR_CSV_GEOM_AS_WKT &&
            panGeomFieldIndex[iField] >= 0 )
        {
            const int iGeom = panGeomFieldIndex[iField];
            OGRGeometry *poGeom = poNewFeature->GetGeomFieldRef(iGeom);
            if( poGeom &&
                poGeom->exportToWkt(&pszEscaped, wkbVariantIso) == OGRERR_NONE )
            {
                const int nLenWKT = static_cast<int>(strlen(pszEscaped));
                char *pszNew =
                    static_cast<char *>(CPLMalloc(1 + nLenWKT + 1 + 1));
                pszNew[0] = '"';
                memcpy(pszNew + 1, pszEscaped, nLenWKT);
                pszNew[1 + nLenWKT] = '"';
                pszNew[1 + nLenWKT + 1] = '\0';
                CPLFree(pszEscaped);
                pszEscaped = pszNew;
            }
            else
            {
                CPLFree(pszEscaped);
                pszEscaped = CPLStrdup("");
            }
        }
        else
        {
            const OGRFieldType eType(
                poFeatureDefn->GetFieldDefn(iField)->GetType());
            if( eType == OFTReal )
            {
                if( poFeatureDefn->GetFieldDefn(iField)->GetSubType() ==
                        OFSTFloat32 &&
                    poNewFeature->IsFieldSetAndNotNull(iField) )
                {
                    pszEscaped = CPLStrdup(CPLSPrintf(
                        "%.8g", poNewFeature->GetFieldAsDouble(iField)));
                }
                else
                {
                    pszEscaped =
                        CPLStrdup(poNewFeature->GetFieldAsString(iField));
                }
            }
            else if( eType == OFTStringList || eType == OFTIntegerList ||
                     eType == OFTInteger64List || eType == OFTRealList )
            {
                char *pszJSon = poNewFeature->GetFieldAsSerializedJSon(iField);
                if( pszJSon )
                {
                    pszEscaped = CPLEscapeString(pszJSon, -1,
                        m_eStringQuoting == StringQuoting::ALWAYS ?
                            CPLES_CSV_FORCE_QUOTING : CPLES_CSV);
                }
                else
                {
                    pszEscaped = CPLStrdup("");
                }
                CPLFree(pszJSon);
            }
            else
            {
                const char* pszContent = poNewFeature->GetFieldAsString(iField);
                pszEscaped = CPLEscapeString(
                    pszContent, -1,
                    (m_eStringQuoting == StringQuoting::ALWAYS ||
                     (m_eStringQuoting == StringQuoting::IF_AMBIGUOUS &&
                      CPLGetValueType(pszContent) != CPL_VALUE_STRING)) ?
                            CPLES_CSV_FORCE_QUOTING : CPLES_CSV);
            }
        }

        const int nLen = static_cast<int>(strlen(pszEscaped));
        bNonEmptyLine |= nLen != 0;
        bool bAddDoubleQuote = false;
        if( chDelimiter == ' ' && pszEscaped[0] != '"' &&
            strchr(pszEscaped, ' ') != nullptr )
            bAddDoubleQuote = true;
        if( bAddDoubleQuote )
            bRet &= VSIFWriteL("\"", 1, 1, fpCSV) > 0;
        if( nLen )
            bRet &= VSIFWriteL(pszEscaped, nLen, 1, fpCSV) > 0;
        if( bAddDoubleQuote )
            bRet &= VSIFWriteL("\"", 1, 1, fpCSV) > 0;
        CPLFree(pszEscaped);
    }

    if( (poFeatureDefn->GetFieldCount() == 1 ||
         (poFeatureDefn->GetFieldCount() == 0 && bHiddenWKTColumn)) &&
        !bNonEmptyLine )
        bRet &= VSIFPrintfL(fpCSV, "%c", chDelimiter) > 0;

    if( bUseCRLF )
        bRet &= VSIFPutcL(13, fpCSV) != EOF;
    bRet &= VSIFPutcL('\n', fpCSV) != EOF;

    if( nTotalFeatures >= 0 )
        nTotalFeatures++;

    return bRet ? OGRERR_NONE : OGRERR_FAILURE;
}

/************************************************************************/
/*                              SetCRLF()                               */
/************************************************************************/

void OGRCSVLayer::SetCRLF(bool bNewValue) { bUseCRLF = bNewValue; }

/************************************************************************/
/*                       SetWriteGeometry()                             */
/************************************************************************/

void OGRCSVLayer::SetWriteGeometry(OGRwkbGeometryType eGType,
                                   OGRCSVGeometryFormat eGeometryFormatIn,
                                   const char *pszGeomCol)
{
    eGeometryFormat = eGeometryFormatIn;
    if( eGeometryFormat == OGR_CSV_GEOM_AS_WKT && eGType != wkbNone )
    {
        OGRGeomFieldDefn oGFld(pszGeomCol, eGType);
        bHiddenWKTColumn = true;
        // We don't use CreateGeomField() since we don't want to generate
        // a geometry field in first position, as it confuses applications
        // (such as MapServer <= 6.4) that assume that the first regular field
        // they add will be at index 0.
        poFeatureDefn->AddGeomFieldDefn(&oGFld);
    }
    else
    {
        poFeatureDefn->SetGeomType(eGType);
    }
}

/************************************************************************/
/*                          SetCreateCSVT()                             */
/************************************************************************/

void OGRCSVLayer::SetCreateCSVT( bool bCreateCSVTIn )
{
    bCreateCSVT = bCreateCSVTIn;
}

/************************************************************************/
/*                          SetWriteBOM()                               */
/************************************************************************/

void OGRCSVLayer::SetWriteBOM( bool bWriteBOMIn ) { bWriteBOM = bWriteBOMIn; }

/************************************************************************/
/*                        GetFeatureCount()                             */
/************************************************************************/

GIntBig OGRCSVLayer::GetFeatureCount( int bForce )
{
    if( m_poFilterGeom != nullptr || m_poAttrQuery != nullptr )
    {
        GIntBig nRet = OGRLayer::GetFeatureCount(bForce);
        if( nRet >= 0 )
        {
            nTotalFeatures = nNextFID - 1;
        }
        return nRet;
    }

    if( nTotalFeatures >= 0 )
        return nTotalFeatures;

    if( fpCSV == nullptr )
        return 0;

    ResetReading();

    if( chDelimiter == '\t' && bDontHonourStrings )
    {
        const int nBufSize = 4096;
        char szBuffer[nBufSize + 1] = {};

        nTotalFeatures = 0;
        bool bLastWasNewLine = false;
        while( true )
        {
            const int nRead =
                static_cast<int>(VSIFReadL(szBuffer, 1, nBufSize, fpCSV));
            szBuffer[nRead] = 0;
            if( nTotalFeatures == 0 && szBuffer[0] != 13 && szBuffer[0] != 10 )
                nTotalFeatures = 1;
            for( int i = 0; i < nRead; i++ )
            {
                if( szBuffer[i] == 13 || szBuffer[i] == 10 )
                {
                    bLastWasNewLine = true;
                }
                else if( bLastWasNewLine )
                {
                    nTotalFeatures++;
                    bLastWasNewLine = false;
                }
            }

            if( nRead < nBufSize )
                break;
        }
    }
    else
    {
        nTotalFeatures = 0;
        while( true )
        {
            char **papszTokens = GetNextLineTokens();
            if( papszTokens == nullptr )
                break;

            nTotalFeatures++;

            CSLDestroy(papszTokens);
        }
    }

    ResetReading();

    return nTotalFeatures;
}

/************************************************************************/
/*                          SyncToDisk()                                */
/************************************************************************/

OGRErr OGRCSVLayer::SyncToDisk()
{
    if( bInWriteMode && fpCSV != nullptr )
    {
        if( VSIFFlushL(fpCSV) != 0 )
            return OGRERR_FAILURE;
    }
    return OGRERR_NONE;
}
